package eu.larkc.plugin.transform;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Iterator;
import java.util.NoSuchElementException;

import javax.jms.Message;

import org.openrdf.model.Resource;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.impl.BNodeImpl;
import org.openrdf.model.impl.LiteralImpl;
import org.openrdf.model.impl.StatementImpl;
import org.openrdf.model.impl.URIImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.rdf.model.RDFNode;
import com.hp.hpl.jena.rdf.model.RDFReader;
import com.hp.hpl.jena.rdf.model.RDFWriter;
import com.hp.hpl.jena.rdf.model.StmtIterator;

import dfdl.exception.DFDLException;
import eu.larkc.core.data.CloseableIterator;
import eu.larkc.core.data.RdfGraphInMemory;
import eu.larkc.core.data.SetOfStatements;
import eu.larkc.core.util.RDFConstants;
import eu.larkc.plugin.Plugin;

/**
 * The DFDLTransformer supports mapping arbitrary ASCII and binary file formats
 * to a data model defined in XML Schema using the XML-based Data Format
 * Description Language (DFDL, forge.gridforum.org/projects/dfdl-wg/), and
 * transforming the file content into RDF triples using Jena GRDDL Reader.
 * 
 * How it works:
 * 
 * [Number]: Transformation Operation ID
 * 
 * (Alphabet): Transformation Resource ID
 * 
 * {Name}-->: Transforming Process
 * 
 * [1]: (a) Raw Text/Binary File + (b) DFDL Description XSD File {DFDL
 * Transforming}--> XML Data
 * 
 * [2]: XML Data + (c) XSL File {GRDDL Transforming}--> Jena RDF Triples
 * 
 * [3]: Jena RDF Triples {Triple Transforming}--> OpenRDF RDF Triples
 * 
 * 
 * 
 * In summary, DFDLTransformer supports the following transformation:
 * 
 * (a) Raw Text/Binary File + (b) DFDL Description XSD File + (c) XSL File -->
 * OpenRDF RDF Triples
 * 
 * 
 * @author Ying Jiang
 * 
 */
public class DFDLTransformer extends Plugin {

	private static final Logger logger = LoggerFactory
			.getLogger(DFDLTransformer.class);

	/**
	 * The output graph URI
	 */
	protected URI outputGraph;

	/**
	 * Property URI for (a) Raw Text/Binary File, from Plugin parameters. The
	 * value of the parameter is the file location of (a).
	 */
	public static final URI DATA_PROPERTY = new URIImpl(
			"http://example.com/DFDL#data");

	/**
	 * Property URI for (b) DFDL Description XSD File, from Plugin parameters.
	 * The value of the parameter is the file location of (b).
	 */
	public static final URI SCHEMA_PROPERTY = new URIImpl(
			"http://example.com/DFDL#schema");

	/**
	 * Property URI for (c) XSL File, from Plugin parameters. The value of the
	 * parameter is the file location of (c).
	 */
	public static final URI XSL_RDF_PROPERTY = new URIImpl(
			"http://example.com/DFDL#xslRdf");

	/**
	 * Defuddle will generate Java source code files and then compile them into
	 * .class files for DFDL transformation. BaseDir is the root directory for
	 * them. By default it's set to the root directory of LarkC platform, which
	 * is also the working directory of the platform.
	 */
	private static final String baseDir = ".";

	/**
	 * When generated Java source code files are compiled, some supporting
	 * classpath jars are required. LibDir is the directory for the jars. By
	 * default, libDir is set to the path of "plugins/DFDLTransformer/lib"
	 * relative to baseDir, which is the plugin's classpath library directory.
	 */
	private static final String libDir = "plugins/DFDLTransformer/lib";

	/**
	 * BuildPath and buildDir are defined for compiling Java source code and
	 * temporary files. They are encapsulated and no need to be changed.
	 */
	private static final String buildPath = "dfdl_temp";
	private static final String buildDir = "build";

	public DFDLTransformer(URI pluginName) {
		super(pluginName);
	}

	@Override
	public void onMessage(Message message) {
	}

	@Override
	protected void initialiseInternal(SetOfStatements workflowDescription) {
		outputGraph = getNamedGraphFromParameters(workflowDescription,
				RDFConstants.DEFAULTOUTPUTNAME);
	}

	public SetOfStatements invokeInternal(SetOfStatements input) {

		// Get the parameter values from workflow description. The values are
		// the file location of (a),(b) and (c)
		String data = this.getPluginParameterValue(DATA_PROPERTY);
		String schema = this.getPluginParameterValue(SCHEMA_PROPERTY);
		String xslRdf = this.getPluginParameterValue(XSL_RDF_PROPERTY);

		// All of the 3 file locations must be available.
		if (data != null && schema != null && xslRdf != null) {

			// The process of [1]: DFDL Transforming
			String packageName = DefuddleUtil.generatePackageName();
			byte[] result = null;
			try {
				result = DefuddleUtil.DFDLTransform(schema, data, packageName,
						baseDir, buildPath, buildDir, libDir, xslRdf);
			} catch (DFDLException e) {
				e.printStackTrace();
				return null;
			} catch (IOException e) {
				e.printStackTrace();
				return null;
			}
			logger.info("Process [1] completed!");

			// The process of [2]: GRDDL Transforming
			ByteArrayInputStream bis = new ByteArrayInputStream(result);

			System.setProperty(
					"jena.grddl.license",
					"The end-user understands the risks associated with running GRDDL software. The end-user agrees to the BSD license for the Jena GRDDL Reader.");
			Model model = ModelFactory.createDefaultModel();
			RDFReader reader = model.getReader("GRDDL");
			reader.read(model, bis, "GRDDL");

			logger.info("Process [2] completed!");

			// The process of [3]: Triple Transforming
			SetOfStatements ret = resultFromJenaModel(outputGraph, model);

			logger.info("Process [3] completed!");
			return ret;

		} else {
			// raise exception
			logger.error("Error! Some parameters are missing!");
			return null;
		}
	}

	public void shutdown() {
	}

	/**
	 * Get a parameter value from the workflow description.
	 * 
	 * @param name
	 *            : The property of the parameter
	 * @return the string value of the parameter
	 */
	private String getPluginParameterValue(URI name) {

		CloseableIterator<Statement> it = this.getPluginParameters()
				.getStatements();
		while (it.hasNext()) {
			Statement s = (Statement) it.next();
			if (s.getPredicate().equals(name)) {
				it.close();
				return s.getObject().stringValue();
			}
		}
		return null;
	}

	private static SetOfStatements resultFromJenaModel(URI target, Model result) {
		final StmtIterator modelData = result.listStatements();
		return new RdfGraphInMemory(target, new Iterable<Statement>() {

			public Iterator<Statement> iterator() {
				return new Iterator<Statement>() {

					public boolean hasNext() {
						return modelData.hasNext();
					}

					public Statement next() {
						if (hasNext() == false) {
							throw new NoSuchElementException();
						}
						com.hp.hpl.jena.rdf.model.Statement st = modelData
								.nextStatement();

						return new StatementImpl((Resource) convert(st
								.getSubject()),
								(URI) convert(st.getPredicate()), convert(st
										.getObject()));
					}

					public void remove() {
					}
				};
			}
		});
	}

	/**
	 * converts Jena RDF nodes to OpenRDF ones
	 * 
	 * @param jena
	 *            RDF value
	 * @return corresponding OpenRDF instance
	 */
	private static Value convert(RDFNode value) {
		if (value instanceof com.hp.hpl.jena.rdf.model.Literal) {
			return new LiteralImpl(value.toString());
		} else if (value instanceof com.hp.hpl.jena.rdf.model.Resource) {
			String str = ((com.hp.hpl.jena.rdf.model.Resource) value).getURI();
			if (str != null)
				return new URIImpl(str);
			else {
				str = ((com.hp.hpl.jena.rdf.model.Resource) value).getId()
						.getLabelString();
				return new BNodeImpl(str);
			}
		} else {
			String str = ((com.hp.hpl.jena.rdf.model.Property) value).getURI();
			return new URIImpl(str);
		}
	}

	/**
	 * We can run a test using main().
	 * 
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {

		String data = "sample_data/HeaderedCSV00.in";
		String schema = "sample_data/HeaderedCSV.xsd";
		String xslRdf = "sample_data/HeaderedCSV.xsl";

		String packageName = DefuddleUtil.generatePackageName();

		byte[] bytes = DefuddleUtil.DFDLTransform(schema, data, packageName,
				baseDir, buildPath, buildDir, "lib", xslRdf);
		ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
		System.setProperty(
				"jena.grddl.license",
				"The end-user understands the risks associated with running GRDDL software. The end-user agrees to the BSD license for the Jena GRDDL Reader.");
		Model model = ModelFactory.createDefaultModel();
		RDFReader reader = model.getReader("GRDDL");
		reader.read(model, bis, "GRDDL");

		SetOfStatements result = resultFromJenaModel(new URIImpl(
				"http://www.eg.com/eg"), model);
		CloseableIterator<Statement> ci = result.getStatements();
		while (ci.hasNext())
			System.out.println(ci.next().toString());

		/*
		 * The print output should be 3 RDF triples. Something like the
		 * followings:
		 * 
		 * (http://example.com/DFDL#myTable,
		 * http://purl.org/dc/elements/1.1/date, "Mon Feb 23 15:20:47 CST 2009")
		 * 
		 * (http://example.com/DFDL#myTable,
		 * http://purl.org/dc/elements/1.1/creator, "NCSA")
		 * 
		 * (http://example.com/DFDL#myTable,
		 * http://www.w3.org/1999/02/22-rdf-syntax-ns#type,
		 * http://example.com/DFDL#Table)
		 */

	}

}